/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.muni.fi.pb138.jprvyd;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.exist.xmldb.EXistResource;
import org.xmldb.api.DatabaseManager;
import org.xmldb.api.base.*;
import org.xmldb.api.modules.XPathQueryService;

/**
 *
 * @author tom
 */
public class ContactManagerImpl implements ContactManager {

    private static String URI = "xmldb:exist://localhost:8080/exist/xmlrpc/db/jprvyd";
    private Database database = new org.exist.xmldb.DatabaseImpl();
    private Collection col;
    private XPathQueryService xpqs;
    private FileHandler handler;
    public static final Logger l = Logger.getLogger("ContactManagerImplLogger");

    public ContactManagerImpl() {
        this(URI);
    }

    public ContactManagerImpl(String URI) {
        try {
            handler = new FileHandler("log/log.log");
            l.addHandler(handler);

            DatabaseManager.registerDatabase(database);
            col = DatabaseManager.getCollection(URI);
            xpqs = (XPathQueryService) col.getService("XPathQueryService", "1.0");
            xpqs.setProperty("indent", "yes");
        } catch (Exception ex) {
            l.log(Level.SEVERE, "Error establishing database connection", ex);
        }
        l.log(Level.INFO, "DB connection established");
    }

    /**
     * Creates a contact
     *
     * @param contact - contact to create
     * @throws IllegalEntityException - when contact argument is invalid
     * @throws IllegalArgumentException - when argument is null
     */
    @Override
    public void createContact(Contact contact) throws IllegalEntityException {

        l.log(Level.INFO, "ContactManagerImpl.createContact: Creating contact");

        if (contact == null) {
            String info = "ContactManagerImpl.createContact: contact is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }
        if (contact.getId() != null) {
            String info = "ContactManagerImpl.createContact: contact id is set";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (contact.getFirstName() == null) {
            String info = "ContactManagerImpl.createContact: first name is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (contact.getSurname() == null) {
            String info = "ContactManagerImpl.createContact: surname is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (contact.getAddress() == null) {
            String info = "ContactManagerImpl.createContact: ico is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (contact.getAddress().getStreet() == null) {
            String info = "ContactManagerImpl.createContact: street is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (contact.getAddress().getCity() == null) {
            String info = "ContactManagerImpl.createContact: city is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (contact.getAddress().getCountry() == null) {
            String info = "ContactManagerImpl.createContact: country is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }
        Integer zip = contact.getAddress().getZip();
        if (zip == null) {
            String info = "ContactManagerImpl.createContact: zip is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (zip < 10000 || zip > 99999) {
            String info = "ContactManagerImpl.createContact: zip is invalid";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        try {
            Long id;
            if (this.getAllContacts().isEmpty()) {
                id = Long.parseLong("1");
            } else {
                id = Long.parseLong(xpqs.query("max(for $x in doc(\"contacts.xml\")//contact return $x/@id)").getIterator().nextResource().getContent().toString());
                id++;
            }

            contact.setId(id);
            String node = getNode(contact);

            String query = "update insert " + node + " into doc(\"contacts.xml\")//contacts";

            ResourceSet res = xpqs.query(query);

        } catch (Exception ex) {
            l.log(Level.SEVERE, "ContactManagerImpl.createContact: Exception creating contact: ", ex);
        }

        l.log(Level.INFO, "ContactManagerImpl.createContact: Contact created");

    }

    private String getNode(Contact contact) {
        String node = "<contact id=\"" + contact.getId() + "\">"
                + "<name>" + contact.getFirstName() + "</name>"
                + "<surname>" + contact.getSurname() + "</surname>"
                + "<address>"
                + "<street>" + contact.getAddress().getStreet() + "</street>"
                + "<city>" + contact.getAddress().getCity() + "</city>"
                + "<country>" + contact.getAddress().getCountry() + "</country>"
                + "<postalcode>" + Integer.toString(contact.getAddress().getZip()) + "</postalcode>"
                + "</address>"
                + "<ico>" + contact.getIco() + "</ico>"
                + "<dic>" + contact.getDic() + "</dic>"
                + "<bankaccount>" + contact.getBankAccount() + "</bankaccount>"
                + "</contact>";
        return node;
    }

    /**
     * removes a contact from db
     *
     * @param id - id of contact to remove
     * @throws IllegalArgumentException when argument id is null
     */
    @Override
    public void removeContact(Long id) {

        l.log(Level.INFO, "ContactManagerImpl.removeContact: Removing contact");

        if (id == null) {
            String info = "ContactManagerImpl.removeContact: id is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }

        try {
            String query = "for $person in doc(\"contacts.xml\")//contact[@id=" + id + "] return update delete $person";
            xpqs.query(query);

        } catch (Exception ex) {
            l.log(Level.SEVERE, "ContactManagerImpl.removeContact: Exception removing contact: ", ex);
        }

        l.log(Level.INFO, "ContactManagerImpl.removeContact: Contact removed");
    }

    /**
     * Updates a contact
     *
     * @param contact to update
     * @throws IllegalEntityException when contact is invalid
     * @throws IllegalArgumentException when contact is null
     */
    @Override
    public void updateContact(Contact contact) throws IllegalEntityException {

        l.log(Level.INFO, "ContactManagerImpl.updateContact:Updating contact");

        if (contact == null) {
            String info = "ContactManagerImpl.updateContact: contact is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }

        if (contact.getId() == null) {
            String info = "ContactManagerImpl.updateContact: id is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        if (contact.getAddress() == null) {
            String info = "ContactManagerImpl.updateContact: address is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }
        
        if (contact.getAddress() == null) {
            String info = "ContactManagerImpl.updateContact: address is null";
            l.log(Level.SEVERE, info);
            throw new IllegalEntityException(info);
        }

        try {
            String node = getNode(contact);

            if (xpqs.query("for $x in doc(\"contacts.xml\")//contact[@id=" + contact.getId() + "] return $x/name").getIterator().hasMoreResources() == false) {
                throw new IllegalEntityException("ContactManagerImpl.updateContact: contact does not exist");
            }


            String query = "update replace doc(\"contacts.xml\")//contact[@id=" + contact.getId() + "] with " + node;
            xpqs.query(query);
        } catch (XMLDBException ex) {
            l.log(Level.SEVERE, "ContactManagerImpl.updateContact: Exception updating contact: ", ex);
        }
        l.log(Level.INFO, "ContactManagerImpl.updateContact: Contact updated");
    }

    /**
     * used by getContactBy* methods to get the result
     *
     * @param where - the where part of XQuery statement
     * @return Contact from databaseF
     */
    private Contact getContactForm(String where) {
        Contact contact = new Contact();
        Address address = new Address();

        try {
            String queryStart = "for $x in doc(\"contacts.xml\")//contact where ";
            String queryEnd = " return data($x/";

            ResourceSet resultName = xpqs.query(queryStart + where + queryEnd + "name)");
            ResourceSet resultSurname = xpqs.query(queryStart + where + queryEnd + "surname)");
            ResourceSet resultIco = xpqs.query(queryStart + where + queryEnd + "ico)");
            ResourceSet resultDic = xpqs.query(queryStart + where + queryEnd + "dic)");
            ResourceSet resultId = xpqs.query(queryStart + where + queryEnd + "@id)");
            ResourceSet resultStreet = xpqs.query(queryStart + where + queryEnd + "address/street)");
            ResourceSet resultPostalcode = xpqs.query(queryStart + where + queryEnd + "address/postalcode)");
            ResourceSet resultCity = xpqs.query(queryStart + where + queryEnd + "address/city)");
            ResourceSet resultCountry = xpqs.query(queryStart + where + queryEnd + "address/country)");
            ResourceSet resultBankAccount = xpqs.query(queryStart + where + queryEnd + "bankaccount)");


            ResourceIterator iterName = resultName.getIterator();
            ResourceIterator iterSurname = resultSurname.getIterator();
            ResourceIterator iterIco = resultIco.getIterator();
            ResourceIterator iterDic = resultDic.getIterator();
            ResourceIterator iterId = resultId.getIterator();
            ResourceIterator iterStreet = resultStreet.getIterator();
            ResourceIterator iterPostalcode = resultPostalcode.getIterator();
            ResourceIterator iterCity = resultCity.getIterator();
            ResourceIterator iterCountry = resultCountry.getIterator();
            ResourceIterator iterBankAccount = resultBankAccount.getIterator();


            Resource resName = null;
            Resource resSurname = null;
            Resource resIco = null;
            Resource resDic = null;
            Resource resId = null;
            Resource resStreet = null;
            Resource resPostalcode = null;
            Resource resCity = null;
            Resource resCountry = null;
            Resource resBankAccount = null;


            try {
                resName = iterName.nextResource();
                resSurname = iterSurname.nextResource();
                resIco = iterIco.nextResource();
                resDic = iterDic.nextResource();
                resId = iterId.nextResource();
                resStreet = iterStreet.nextResource();
                resPostalcode = iterPostalcode.nextResource();
                resCity = iterCity.nextResource();
                resCountry = iterCountry.nextResource();
                resBankAccount = iterBankAccount.nextResource();


                contact.setFirstName(resName.getContent().toString());
                contact.setSurname(resSurname.getContent().toString());
                contact.setIco(Integer.parseInt(resIco.getContent().toString()));
                if (resDic != null) {
                contact.setDic(resDic.getContent().toString());
                }
                contact.setId(Long.parseLong(resId.getContent().toString()));
                if (resDic != null) {
                contact.setBankAccount(resBankAccount.getContent().toString());
                }
                address.setStreet(resStreet.getContent().toString());
                address.setZip(Integer.parseInt(resPostalcode.getContent().toString()));
                address.setCity(resCity.getContent().toString());
                address.setCountry(resCountry.getContent().toString());
                contact.setAddress(address);

            } catch (Exception ex) {
                l.log(Level.SEVERE, "ContactManagerImpl.getContactForm: Exception getting contact: ", ex);
                throw new Exception(ex);
            } finally {
                try {
                    ((EXistResource) resName).freeResources();
                    ((EXistResource) resSurname).freeResources();
                    ((EXistResource) resIco).freeResources();
                    if (resDic != null) {
                    ((EXistResource) resDic).freeResources();
                    }
                    ((EXistResource) resId).freeResources();
                    if (resDic != null) {
                    ((EXistResource) resBankAccount).freeResources();
                    }
                    ((EXistResource) resStreet).freeResources();
                    ((EXistResource) resPostalcode).freeResources();
                    ((EXistResource) resCity).freeResources();
                    ((EXistResource) resCountry).freeResources();
                } catch (Exception ex) {
                    l.log(Level.SEVERE, "ContactManagerImpl.getContactForm: Exception freeing up resources: ", ex);
                }

            }

        } catch (Exception ex) {
            l.log(Level.SEVERE, "ContactManagerImpl.getContactForm: Excepion occured: ", ex);
        } finally {

            if (col != null) {
                try {
                    col.close();
                } catch (XMLDBException ex) {
                    l.log(Level.SEVERE, "ContactManagerImpl.getContactForm: Exception closing collection: ", ex);
                }
            }
        }
        l.log(Level.INFO, "ContactManagerImpl.getContactForm: Contact retrieved");
        if (contact.getId() == null) {
            l.log(Level.INFO, "ContactManagerImpl.getContactForm: No contact found");
            return null;
        } else {
            l.log(Level.INFO, "ContactManagerImpl.getContactForm: Contact retrieved");
            return contact;
        }
    }

    /**
     * used by getContactsBy* method
     *
     * @param where where part of the XQuery statement
     * @return List of contacts from database
     */
    private List<Contact> getContactsForm(String where) {
        List<Contact> contacts = new ArrayList<Contact>();

        try {

            String queryStart = "for $x in doc(\"contacts.xml\")//contact ";
            String queryEnd = " return data($x/";

            ResourceSet resultName = xpqs.query(queryStart + where + queryEnd + "name)");
            ResourceSet resultSurname = xpqs.query(queryStart + where + queryEnd + "surname)");
            ResourceSet resultIco = xpqs.query(queryStart + where + queryEnd + "ico)");
            ResourceSet resultDic = xpqs.query(queryStart + where + queryEnd + "dic)");
            ResourceSet resultId = xpqs.query(queryStart + where + queryEnd + "@id)");
            ResourceSet resultStreet = xpqs.query(queryStart + where + queryEnd + "address/street)");
            ResourceSet resultPostalcode = xpqs.query(queryStart + where + queryEnd + "address/postalcode)");
            ResourceSet resultCity = xpqs.query(queryStart + where + queryEnd + "address/city)");
            ResourceSet resultCountry = xpqs.query(queryStart + where + queryEnd + "address/country)");
            ResourceSet resultBankAccount = xpqs.query(queryStart + where + queryEnd + "bankaccount)");

            ResourceIterator iterName = resultName.getIterator();
            ResourceIterator iterSurname = resultSurname.getIterator();
            ResourceIterator iterIco = resultIco.getIterator();
            ResourceIterator iterDic = resultDic.getIterator();
            ResourceIterator iterId = resultId.getIterator();
            ResourceIterator iterStreet = resultStreet.getIterator();
            ResourceIterator iterPostalcode = resultPostalcode.getIterator();
            ResourceIterator iterCity = resultCity.getIterator();
            ResourceIterator iterCountry = resultCountry.getIterator();
            ResourceIterator iterBankAccount = resultBankAccount.getIterator();

            Resource resName = null;
            Resource resSurname = null;
            Resource resIco = null;
            Resource resDic = null;
            Resource resId = null;
            Resource resStreet = null;
            Resource resPostalcode = null;
            Resource resCity = null;
            Resource resCountry = null;
            Resource resBankAccount = null;

            while (iterName.hasMoreResources()) {

                try {
                    Contact contact = new Contact();
                    Address address = new Address();

                    resName = iterName.nextResource();
                    resSurname = iterSurname.nextResource();
                    resIco = iterIco.nextResource();
                    resDic = iterDic.nextResource();
                    resId = iterId.nextResource();
                    resStreet = iterStreet.nextResource();
                    resPostalcode = iterPostalcode.nextResource();
                    resCity = iterCity.nextResource();
                    resCountry = iterCountry.nextResource();
                    resBankAccount = iterBankAccount.nextResource();

                    contact.setFirstName(resName.getContent().toString());
                    contact.setSurname(resSurname.getContent().toString());
                    contact.setIco(Integer.parseInt(resIco.getContent().toString()));
                    if (resDic != null) {
                    contact.setDic(resDic.getContent().toString());
                    }
                    contact.setId(Long.parseLong(resId.getContent().toString()));
                    if (resBankAccount != null) {
                    contact.setBankAccount(resBankAccount.getContent().toString());
                    }
                    address.setStreet(resStreet.getContent().toString());
                    address.setZip(Integer.parseInt(resPostalcode.getContent().toString()));
                    address.setCity(resCity.getContent().toString());
                    address.setCountry(resCountry.getContent().toString());
                    contact.setAddress(address);

                    contacts.add(contact);


                } finally {

                    try {
                        ((EXistResource) resName).freeResources();
                        ((EXistResource) resSurname).freeResources();
                        ((EXistResource) resIco).freeResources();
                        if (resDic != null) {
                        ((EXistResource) resDic).freeResources();
                        }
                        ((EXistResource) resId).freeResources();
                        if (resDic != null) {
                        ((EXistResource) resBankAccount).freeResources();
                        }
                        ((EXistResource) resStreet).freeResources();
                        ((EXistResource) resPostalcode).freeResources();
                        ((EXistResource) resCity).freeResources();
                        ((EXistResource) resCountry).freeResources();
                    } catch (Exception ex) {
                        l.log(Level.SEVERE, "ContactManagerImpl.getContactsForm: Exception occured: ", ex);

                    }
                }
            }
        } catch (Exception ex) {
            l.log(Level.SEVERE, "ContactManagerImpl.getContactsForm: Exception occured: ", ex);
        } finally {

            if (col != null) {
                try {
                    col.close();
                } catch (XMLDBException ex) {
                    l.log(Level.SEVERE, "ContactManagerImpl.getContactsForm: Exception occured: ", ex);
                }
            }
        }
        l.log(Level.INFO, "ContactManagerImpl.getContactsForm: Contacts retrieved");
        return contacts;
    }

//nemalo by sa sem pridat aj getContactByName? a ked ano, tak vyhladavat
//len podla priezviska, alebo podla priezviska a krstneho mena?
    /**
     * returns contact with specific id
     *
     * @param id - id of contact to retrieve from db
     * @return contact with specified id
     */
    @Override
    public Contact getContactById(Long id) {
        l.log(Level.INFO, "ContactManagerImpl.getContactById: Retrieving contact");

        if (id == null) {
            String info = "ContactManagerImpl.getContactById: id is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }

        String where = "@id=" + Long.toString(id);

        return getContactForm(where);
    }

    /**
     * returns contact with specific ico
     *
     * @param ico - ico of contact to retrieve from db
     * @return person with specified ico
     */
    @Override
    public Contact getContactByIco(int ico) {
        l.log(Level.INFO, "ContactManagerImpl.getContactByIco: Retrieving contact");

        Integer i = ico;
        if (i == null) {
            String info = "ContactManagerImpl.getContactByIco: ico is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }

        return getContactForm("ico = " + Integer.toString(ico));
    }

    /**
     * returns contact with specific div
     *
     * @param dic - dic of contact to retrieve from db
     * @return person with specified dic
     */
    @Override
    public Contact getContactByDic(String dic) {
        l.log(Level.INFO, "ContactManagerImpl.getContactById: Retrieving contact");

        if (dic == null) {
            String info = "ContactManagerImpl.getContactByDic: dic is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }
        return getContactForm("dic = \"" + dic + "\"");
    }

    /**
     * returns contact with specific bank account
     *
     * @param bankAccount - ico of contact to retrieve from bank account
     * @return person with specified bank account
     */
    @Override
    public Contact getContactByBankAccount(String bankAccount) {
        l.log(Level.INFO, "ContactManagerImpl.getContactByBankAccount: Retrieving contact");
        if (bankAccount == null) {
            String info = "ContactManagerImpl.getContactByBankAccount: bankAccount is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }
        return getContactForm("bankaccount = " + bankAccount);
    }

    /**
     * returns all contacts
     *
     * @return list of all contacts
     */
    @Override
    public List<Contact> getAllContacts() {
        l.log(Level.INFO, "ContactManagerImpl.getContactByBankAccount: Retrieving contacts");
        return getContactsForm("");
    }

    /**
     * get all contacts with the specific address
     *
     * @param address - address of contacts to retrieve
     * @return list of all contacts with specified address
     */
    @Override
    public List<Contact> getContactsByAddress(Address address) {
        l.log(Level.INFO, "ContactManagerImpl.getContactAddress: Retrieving contacts");

        if (address == null) {
            String info = "ContactManagerImpl.getContactsByAddress: address is null";
            l.log(Level.SEVERE, info);
            throw new IllegalArgumentException(info);
        }

        return getContactsForm("where address/street=\"" + address.getStreet()
                + "\" and address/city=\"" + address.getCity() + "\" and address/postalcode=\""
                + Long.toString(address.getZip()) + "\" and address/country=\"" + address.getCountry()
                + "\"");
    }
}
